Example 2 Clustering Geometric ObjectsΒΆ

In this example we will look at a few of the tools provided by the clifford package for (4,1) conformal geometric algebra (CGA) and see how we can use them in a practical setting to cluster geometric objects via the simple K-means clustering algorithm provided in clifford.tools

As before the first step in using the package for CGA is to generate and import the algebra:

[1]:
from clifford.g3c import *
print('e1*e1 ', e1*e1)
print('e2*e2 ', e2*e2)
print('e3*e3 ', e3*e3)
print('e4*e4 ', e4*e4)
print('e5*e5 ', e5*e5)
e1*e1  1.0
e2*e2  1.0
e3*e3  1.0
e4*e4  1.0
e5*e5  -1.0

The tools submodule of the clifford package contains a wide array of algorithms and tools that can be useful for manipulating objects in CGA. In this case we will be generating a large number of objects and then segmenting them into clusters.

We first need an algorithm for generating a cluster of objects in space. We will construct this cluster by generating a random object and then repeatedly disturbing this object by some small fixed amount and storing the result:

[2]:
from clifford.tools.g3c import *
import numpy as np

def generate_random_object_cluster(n_objects, object_generator, max_cluster_trans=1.0, max_cluster_rot=np.pi/8):
    """ Creates a cluster of random objects """
    ref_obj = object_generator()
    cluster_objects = []
    for i in range(n_objects):
        r = random_rotation_translation_rotor(maximum_translation=max_cluster_trans, maximum_angle=max_cluster_rot)
        new_obj = apply_rotor(ref_obj, r)
        cluster_objects.append(new_obj)
    return cluster_objects

We can use this function to create a cluster and then we can visualise this cluster with GAOnline using the built in tools in clifford.

[3]:
from clifford.tools.g3c.GAOnline import *
clustered_circles = generate_random_object_cluster(10, random_circle)
sc = GAScene()
for c in clustered_circles:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.42221^e123) - (2.1175^e124) - (2.11954^e125) + (9.86799^e134) + (9.92021^e135) + (0.21426^e145) - (3.04977^e234) - (3.05633^e235) - (0.01816^e245) - (0.22397^e345),rgb(255,0,0));
DrawCircle(-(0.4207^e123) - (2.76222^e124) - (2.77512^e125) + (9.43367^e134) + (9.4839^e135) + (0.04047^e145) - (4.02621^e234) - (4.03537^e235) + (0.06337^e245) - (0.27541^e345),rgb(255,0,0));
DrawCircle(-(0.46844^e123) - (3.09349^e124) - (3.10375^e125) + (9.07284^e134) + (9.12388^e135) + (0.13835^e145) - (4.72152^e234) - (4.72943^e235) + (0.05117^e245) - (0.36123^e345),rgb(255,0,0));
DrawCircle(-(0.41639^e123) - (3.47284^e124) - (3.48154^e125) + (8.5898^e134) + (8.64087^e135) + (0.24637^e145) - (4.09867^e234) - (4.10814^e235) + (0.0067^e245) - (0.30734^e345),rgb(255,0,0));
DrawCircle(-(0.45305^e123) - (4.27255^e124) - (4.2836^e125) + (9.06068^e134) + (9.1121^e135) + (0.26382^e145) - (3.08053^e234) - (3.08321^e235) + (0.04988^e245) - (0.296^e345),rgb(255,0,0));
DrawCircle(-(0.44801^e123) - (4.05467^e124) - (4.06481^e125) + (9.84988^e134) + (9.90118^e135) + (0.24134^e145) - (2.34947^e234) - (2.35566^e235) - (0.00287^e245) - (0.13287^e345),rgb(255,0,0));
DrawCircle(-(0.49094^e123) - (5.37262^e124) - (5.38871^e125) + (9.19435^e134) + (9.24444^e135) + (0.24676^e145) - (2.92126^e234) - (2.92358^e235) + (0.07041^e245) - (0.25466^e345),rgb(255,0,0));
DrawCircle(-(0.5993^e123) - (6.05004^e124) - (6.06374^e125) + (9.06575^e134) + (9.11557^e135) + (0.29575^e145) - (5.43489^e234) - (5.44506^e235) + (0.02158^e245) - (0.29802^e345),rgb(255,0,0));
DrawCircle(-(0.37541^e123) - (3.86829^e124) - (3.88199^e125) + (8.93879^e134) + (8.98888^e135) + (0.18999^e145) - (3.14755^e234) - (3.15629^e235) + (0.02481^e245) - (0.21193^e345),rgb(255,0,0));
DrawCircle(-(0.38686^e123) - (5.73157^e124) - (5.7554^e125) + (8.19387^e134) + (8.24084^e135) + (0.19116^e145) - (1.93461^e234) - (1.9348^e235) + (0.11639^e245) - (0.23092^e345),rgb(255,0,0));

This cluster generation function appears in clifford tools by default and it can be imported as follows:

[4]:
from clifford.tools.g3c import generate_random_object_cluster

Now that we can generate individual clusters we would like to generate many:

[5]:
def generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster ):
    object_clusters = []
    for i in range(n_clusters):
        cluster_objects = generate_random_object_cluster(n_objects_per_cluster, object_generator,
                                                         max_cluster_trans=0.5, max_cluster_rot=np.pi / 16)
        object_clusters.append(cluster_objects)
    all_objects = [item for sublist in object_clusters for item in sublist]
    return all_objects, object_clusters

Again this function appears by default in clifford tools and we can easily visualise the result:

[6]:
from clifford.tools.g3c import generate_n_clusters

all_objects, object_clusters = generate_n_clusters(random_circle, 2, 5)
sc = GAScene()
for c in all_objects:
    sc.add_circle(c,'rgb(255,0,0)')
print(sc)
DrawCircle(-(0.01668^e123) + (3.1929^e124) + (3.21484^e125) + (5.81733^e134) + (5.85409^e135) + (0.61478^e145) + (1.5356^e234) + (1.5456^e235) + (0.10512^e245) - (0.10415^e345),rgb(255,0,0));
DrawCircle(-(0.0186^e123) + (2.30126^e124) + (2.31861^e125) + (5.94359^e134) + (5.98328^e135) + (0.63569^e145) + (1.05093^e234) + (1.05843^e235) + (0.05222^e245) - (0.15543^e345),rgb(255,0,0));
DrawCircle(-(0.00962^e123) + (3.27827^e124) + (3.30166^e125) + (5.35273^e134) + (5.38907^e135) + (0.63018^e145) + (1.13803^e234) + (1.14611^e235) + (0.01122^e245) - (0.20044^e345),rgb(255,0,0));
DrawCircle(-(0.00439^e123) + (2.58354^e124) + (2.60202^e125) + (5.60645^e134) + (5.64545^e135) + (0.64713^e145) + (1.18622^e234) + (1.19459^e235) + (0.06867^e245) - (0.14812^e345),rgb(255,0,0));
DrawCircle(-(0.03435^e123) + (2.0082^e124) + (2.02426^e125) + (6.45598^e134) + (6.49678^e135) + (0.63162^e145) + (0.43639^e234) + (0.43955^e235) + (0.01887^e245) - (0.07659^e345),rgb(255,0,0));
DrawCircle(-(0.54406^e123) + (1.46338^e124) + (1.45948^e125) + (0.58708^e134) + (0.59059^e135) - (0.01366^e145) + (13.00937^e234) + (13.05742^e235) - (0.2225^e245) + (0.03216^e345),rgb(255,0,0));
DrawCircle(-(0.544^e123) - (0.9983^e124) - (1.01077^e125) + (2.3164^e134) + (2.32375^e135) - (0.03958^e145) + (12.88248^e234) + (12.9286^e235) - (0.2105^e245) - (0.02235^e345),rgb(255,0,0));
DrawCircle(-(0.53367^e123) - (0.46146^e124) - (0.47345^e125) - (0.63077^e134) - (0.62992^e135) + (0.01491^e145) + (12.86581^e234) + (12.91262^e235) - (0.24861^e245) + (0.07577^e345),rgb(255,0,0));
DrawCircle(-(0.54145^e123) - (1.40553^e124) - (1.41969^e125) + (1.11592^e134) + (1.11996^e135) - (0.01868^e145) + (12.96224^e234) + (13.00828^e235) - (0.21925^e245) + (0.00181^e345),rgb(255,0,0));
DrawCircle(-(0.48336^e123) - (0.0639^e124) - (0.07445^e125) + (1.20151^e134) + (1.20442^e135) - (0.02584^e145) + (12.23798^e234) + (12.28506^e235) - (0.26087^e245) - (0.0432^e345),rgb(255,0,0));

Given that we can now generate multiple clusters of objects we can test algorithms for segmenting them.

The function run_n_clusters below generates a lot of objects distributed into n clusters and then attempts to segment the objects to recover the clusters.

[7]:
from clifford.tools.g3c.object_clustering import n_clusters_objects
import time

def run_n_clusters( object_generator, n_clusters, n_objects_per_cluster, n_shotgunning):
    all_objects, object_clusters = generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster )
    [new_labels, centroids, start_labels, start_centroids] = n_clusters_objects(n_clusters, all_objects,
                                                                                initial_centroids=None,
                                                                                n_shotgunning=n_shotgunning,
                                                                                averaging_method='unweighted')
    return all_objects, new_labels, centroids

Lets try it!

[8]:
from clifford.tools.g3c.object_clustering import visualise_n_clusters

object_generator = random_circle

n_clusters = 3
n_objects_per_cluster = 10
n_shotgunning = 60
all_objects, labels, centroids = run_n_clusters(object_generator, n_clusters,
                                                     n_objects_per_cluster, n_shotgunning)

sc = visualise_n_clusters(all_objects, centroids, labels, object_type='circle',
                     color_1=np.array([255, 0, 0]), color_2=np.array([0, 255, 0]))
print(sc)
DrawCircle(-(0.03501^e123) - (2.265^e124) - (2.30037^e125) + (1.37739^e134) + (1.39585^e135) - (0.19732^e145) + (3.59562^e234) + (3.65609^e235) + (0.27905^e245) - (0.48293^e345),rgb(127, 127, 0));
DrawCircle(-(0.04612^e123) - (2.54712^e124) - (2.58602^e125) + (1.43445^e134) + (1.45226^e135) - (0.22611^e145) + (3.51023^e234) + (3.5687^e235) + (0.26825^e245) - (0.46268^e345),rgb(127, 127, 0));
DrawCircle((0.00726^e123) - (2.32529^e124) - (2.36404^e125) + (0.90696^e134) + (0.92275^e135) - (0.21483^e145) + (3.6087^e234) + (3.66784^e235) + (0.32058^e245) - (0.45845^e345),rgb(127, 127, 0));
DrawCircle((0.03829^e123) - (2.31818^e124) - (2.36122^e125) + (0.74356^e134) + (0.76167^e135) - (0.26041^e145) + (3.28092^e234) + (3.33631^e235) + (0.33463^e245) - (0.47589^e345),rgb(127, 127, 0));
DrawCircle(-(0.01127^e123) - (2.45005^e124) - (2.48819^e125) + (0.34792^e134) + (0.35217^e135) - (0.25257^e145) + (3.86711^e234) + (3.92856^e235) + (0.27316^e245) - (0.43745^e345),rgb(127, 127, 0));
DrawCircle((0.00501^e123) - (3.05172^e124) - (3.09752^e125) + (1.04118^e134) + (1.05727^e135) - (0.28486^e145) + (3.60697^e234) + (3.66075^e235) + (0.21255^e245) - (0.40921^e345),rgb(127, 127, 0));
DrawCircle((0.08537^e123) - (2.29492^e124) - (2.33631^e125) + (0.3628^e134) + (0.37805^e135) - (0.2339^e145) + (3.8567^e234) + (3.91417^e235) + (0.32476^e245) - (0.44442^e345),rgb(127, 127, 0));
DrawCircle(-(0.01766^e123) - (2.61234^e124) - (2.65458^e125) + (1.01619^e134) + (1.03084^e135) - (0.26248^e145) + (3.4065^e234) + (3.46351^e235) + (0.28581^e245) - (0.45345^e345),rgb(127, 127, 0));
DrawCircle((0.03707^e123) - (2.20677^e124) - (2.24996^e125) + (0.37653^e134) + (0.38809^e135) - (0.24921^e145) + (3.26628^e234) + (3.32329^e235) + (0.41167^e245) - (0.43911^e345),rgb(127, 127, 0));
DrawCircle((0.006^e123) - (2.34368^e124) - (2.38294^e125) + (0.84683^e134) + (0.86167^e135) - (0.25283^e145) + (3.56611^e234) + (3.62517^e235) + (0.2644^e245) - (0.48023^e345),rgb(127, 127, 0));
DrawCircle(-(0.64424^e123) - (2.08805^e124) - (2.08978^e125) + (9.8189^e134) + (9.8766^e135) + (0.16057^e145) + (1.50877^e234) + (1.54171^e235) + (0.10271^e245) - (0.36694^e345),rgb(255, 0, 0));
DrawCircle(-(0.58262^e123) - (1.53975^e124) - (1.53862^e125) + (9.29198^e134) + (9.35104^e135) + (0.17416^e145) + (1.37289^e234) + (1.40334^e235) + (0.08315^e245) - (0.34651^e345),rgb(255, 0, 0));
DrawCircle(-(0.62367^e123) - (2.91175^e124) - (2.92445^e125) + (9.30713^e134) + (9.36122^e135) + (0.06316^e145) + (1.87299^e234) + (1.90945^e235) + (0.13211^e245) - (0.38164^e345),rgb(255, 0, 0));
DrawCircle(-(0.59109^e123) - (3.24213^e124) - (3.24906^e125) + (8.5796^e134) + (8.63274^e135) + (0.191^e145) + (2.5782^e234) + (2.6175^e235) + (0.18534^e245) - (0.33857^e345),rgb(255, 0, 0));
DrawCircle(-(0.61678^e123) - (2.13585^e124) - (2.13935^e125) + (9.10142^e134) + (9.1542^e135) + (0.13101^e145) + (3.52112^e234) + (3.56136^e235) + (0.11932^e245) - (0.29249^e345),rgb(255, 0, 0));
DrawCircle(-(0.61488^e123) - (2.32015^e124) - (2.32577^e125) + (9.39971^e134) + (9.45617^e135) + (0.12708^e145) + (1.86994^e234) + (1.90455^e235) + (0.1135^e245) - (0.35742^e345),rgb(255, 0, 0));
DrawCircle(-(0.58257^e123) - (1.60204^e124) - (1.60695^e125) + (9.01227^e134) + (9.06535^e135) + (0.07009^e145) + (2.72814^e234) + (2.76784^e235) + (0.08621^e245) - (0.36564^e345),rgb(255, 0, 0));
DrawCircle(-(0.5592^e123) - (1.09217^e124) - (1.08829^e125) + (9.02123^e134) + (9.07849^e135) + (0.17454^e145) + (2.11318^e234) + (2.14669^e235) + (0.08015^e245) - (0.32427^e345),rgb(255, 0, 0));
DrawCircle(-(0.59998^e123) - (2.18925^e124) - (2.19153^e125) + (9.17457^e134) + (9.23104^e135) + (0.17114^e145) + (2.62071^e234) + (2.65568^e235) + (0.11762^e245) - (0.28803^e345),rgb(255, 0, 0));
DrawCircle(-(0.58085^e123) - (1.5849^e124) - (1.5848^e125) + (9.28801^e134) + (9.34709^e135) + (0.16275^e145) + (1.608^e234) + (1.63845^e235) + (0.08336^e245) - (0.32338^e345),rgb(255, 0, 0));
DrawCircle((0.03001^e123) - (0.39712^e124) - (0.38865^e125) + (0.96536^e134) + (0.9552^e135) - (0.13787^e145) - (4.24518^e234) - (4.2211^e235) + (0.87894^e245) - (0.66286^e345),rgb(0, 255, 0));
DrawCircle((0.04535^e123) - (0.40892^e124) - (0.39735^e125) + (0.50924^e134) + (0.50009^e135) - (0.04737^e145) - (4.39001^e234) - (4.36684^e235) + (0.91089^e245) - (0.62578^e345),rgb(0, 255, 0));
DrawCircle((0.03739^e123) + (0.05834^e124) + (0.06872^e125) + (0.21361^e134) + (0.20458^e135) - (0.07337^e145) - (3.05245^e234) - (3.02867^e235) + (0.88429^e245) - (0.60093^e345),rgb(0, 255, 0));
DrawCircle((0.02518^e123) - (0.35479^e124) - (0.34375^e125) + (0.3571^e134) + (0.34851^e135) - (0.03558^e145) - (2.76623^e234) - (2.74258^e235) + (0.87963^e245) - (0.60799^e345),rgb(0, 255, 0));
DrawCircle((0.00813^e123) - (1.33769^e124) - (1.32626^e125) + (0.73103^e134) + (0.72471^e135) + (0.01298^e145) - (3.5036^e234) - (3.47943^e235) + (0.94994^e245) - (0.55311^e345),rgb(0, 255, 0));
DrawCircle((0.03547^e123) - (0.09548^e124) - (0.08568^e125) + (0.39124^e134) + (0.38056^e135) - (0.0794^e145) - (3.17589^e234) - (3.15256^e235) + (0.81506^e245) - (0.6988^e345),rgb(0, 255, 0));
DrawCircle((0.01487^e123) - (0.86961^e124) - (0.85875^e125) + (0.72399^e134) + (0.71527^e135) - (0.01933^e145) - (3.08096^e234) - (3.05728^e235) + (0.86629^e245) - (0.65273^e345),rgb(0, 255, 0));
DrawCircle((0.03838^e123) - (0.03596^e124) - (0.02506^e125) + (0.19047^e134) + (0.18174^e135) - (0.0459^e145) - (3.19501^e234) - (3.17135^e235) + (0.88495^e245) - (0.60892^e345),rgb(0, 255, 0));
DrawCircle((0.00213^e123) - (0.78602^e124) - (0.77813^e125) + (1.27038^e134) + (1.2582^e135) - (0.21445^e145) - (2.54724^e234) - (2.52391^e235) + (0.83228^e245) - (0.65019^e345),rgb(0, 255, 0));
DrawCircle((0.03063^e123) - (0.49887^e124) - (0.48857^e125) + (0.58124^e134) + (0.57161^e135) - (0.03857^e145) - (3.6419^e234) - (3.61832^e235) + (0.8401^e245) - (0.69725^e345),rgb(0, 255, 0));
DrawCircle((0.02693^e123) - (0.46814^e124) - (0.45784^e125) + (0.60302^e134) + (0.59367^e135) - (0.06829^e145) - (3.37007^e234) - (3.34635^e235) + (0.87696^e245) - (0.63799^e345),rgb(0,0,0));
DrawCircle((0.00721^e123) - (2.45545^e124) - (2.49612^e125) + (0.8453^e134) + (0.86001^e135) - (0.24359^e145) + (3.56186^e234) + (3.61998^e235) + (0.29816^e245) - (0.456^e345),rgb(0,0,0));
DrawCircle(-(0.60316^e123) - (2.08174^e124) - (2.08504^e125) + (9.2572^e134) + (9.31344^e135) + (0.14343^e145) + (2.18901^e234) + (2.22452^e235) + (0.11058^e245) - (0.34091^e345),rgb(0,0,0));